# scrapy 框架需要采用命令行进行创建与运行
# scrapy startproject 项目名——> cd 项目名（切换路径）——>scrapy genspider 文件名——>scrapy crawl 文件名（运行）
# 用cmd切换到要pycharm的项目文件夹用上述方法建立项目再用
# scrapy startproject 项目名——>给文件夹改个名字 ——>用pycharm创建同名项目（用python的编译器，别用charm的）
# ——>把更名后的scrapy中的文件复制到用pycharm创建的同名项目中——>charm的命令行中：scrapy genspider 文件名
# settings.py中需要打开一些设置，具体参照settings.py
# 机器人协议一定要置为False，ROBOTSTXT_OBEY = False；USER_AGENT也要打开填写一下；还有ITEM_PIPELINES
# 需要储存的要在item中建立相应的对象，在这里将数据yield给item，在pipelines中进行保存
# 调试时可以在下面输入scrapy shell 进行调试

import scrapy
# 得把items导入进来
# 如果在charm的目录中创建的，导入的时候得少写一层文件夹，不然运行的时候scrapy会报错
# 还可以先在charm中先创建一个scrapy的项目，再用charm创建一个同名的把文件复制过来，这样就不会提示错误了
from spider.items import MovieItem, MovieCommentItem, MoviePlayUrlItem, DetailItem, MovieImage, UserHead
from time import sleep

def print_save_txt(content):
    print(content)
    with open('note.txt','a',encoding='utf-8') as file:
        file.write(content)
        file.flush()

def check_response(func):
    def inner(self, response, *args, **kwargs):
        if response.status == 200:
            return func(self, response, *args, **kwargs)
        else:
            print_save_txt('请求失败{}'.format(response))
    return inner

class XpcspiderSpider(scrapy.Spider):
    #一些地址中可能带有\t,之类的转义字符，为防止出现错误可以url = r'http://…………'
    name = 'XPC'
    #允许爬取的地址，如果scrapy.Request里面设置了dont_filter=True,可以不管
    allowed_domains = ['www.xinpianchang.com/','app2.xinpianchang.com', 'mod-api.xinpianchang.com']
    #最开始爬取的页面
    start_urls = ['https://www.xinpianchang.com/channel/index/id-1/sort-like?from=navigator']
    #用于拼接地址
    base_url = "http://www.xinpianchang.com"
    #视频详情页面的地址
    detail_base_url = "https://www.xinpianchang.com/a{}?from=ArticleList"
    # 评论页面的地址，用来与下一页达到链接做拼接
    comment_host_url = "http://app2.xinpianchang.com"
    #评论页面的地址，per_page=100是设置每页多少条评论
    comment_base_url = "https://app2.xinpianchang.com/comments?resource_id={}&type=article&page=1&per_page=100"
    #视频播放地址
    play_base_url = "https://mod-api.xinpianchang.com/mod/api/v2/media/{}?appKey=61a2f329348b3bf77&extend=userInfo%2CuserStatus"

    # 默认的回调函数，框架根据start_urls发送请求，响应默认的回传函数（用yield回传）
    #这里的 respons 返回的是 start_urls 的request结果
    #在重写这个函数的时候需要添加一个参数**kwargs
    @check_response
    def parse(self, response,**kwargs):
        print('——1电影列表页: {}'.format(response))
        print('*'*30)
        lis = response.xpath('//li[@class="enter-filmplay"]')
        for movie_element in lis:
            # xpath返回的如果是一个列表，需要用.extract_first()获取第一个元素
            # 或者只要一个列表的话.extract()就好
            #若要继续遍历元素则不要加
            movie_title = movie_element.xpath('./div/div/a/p/@title').extract_first()
            movie_desc = movie_element.xpath('./a/div/div[@class="fs_12 fw_300 c_w_f desc line-hide-3"]/text()').extract_first()
            movie_id = movie_element.xpath('./@data-articleid').extract_first()
            movie_duration = movie_element.xpath('./a/span/text()').extract_first()
            movie_publish = movie_element.xpath('./a/div/p/text()').extract_first()
            movie_play = movie_element.xpath('./div/div/div/span[@class="fw_300 icon-play-volume"]/text()').extract_first()
            movie_like = movie_element.xpath('./div/div/div/span[@class="fw_300 c_b_9 icon-like"]/text()').extract_first()
            image_url = movie_element.xpath('./a/img/@_src').extract_first()
            # 如果不用储存可以直接通过yield打印出来
            # yield{"title":movie_title,
            #       "desc":movie_desc,
            #       "id":movie_id,
            #       "duration":movie_duration}

            # 要保存文件的话需要通过items传递给pipelines，所以需要在items.py中加入相应的item
            # 先用对应的item建立实例，再用字典赋值的方法向实例中传值，最后通过yield函数将实例传递
            item = MovieItem()
            item["movie_title"] = movie_title #标题
            item["movie_desc"] = movie_desc # 视频说明
            item["movie_id"] = movie_id # movie_id
            item["movie_duration"] = movie_duration #时间
            item["movie_publish"] = movie_publish #发布时间
            item["movie_play"] = movie_play #播放数量
            item["movie_like"] = movie_like #喜欢数量
            item["image_url"] = image_url #图片地址
            yield item

            # 将请求提交给 Scrapy Engine    两个参数：url请求地址；callback 回调函数，本次请求结果返回的地方
            # yield scrapy.Request是这里的灵魂，在这里调用进行下一层级页面抓取的方法，逐层抓取
            # 在这里，yield 相当于是一个可以不断返回的 return（一个函数只能return一个值，yield可以不断返回），yield 本身是用来做generator（生成器）的，与next()配合使用
            # 在这一层级抓取到的信息可以通过 meta参数 以字典形式传递给 callback 中的方法
            # 参数带callback的一般都是回调的方法
            # 有时前面设置的允许爬的网站可能出问题，可以直接设置参数 dont_filter=True 取消网址过滤器
            yield scrapy.Request(url=self.detail_base_url.format(movie_id), callback=self.detail_parse,meta={"movie_id": movie_id},dont_filter=True)
            yield scrapy.Request(url=image_url, callback=self.movie_image_parse,meta={"movie_id": movie_id},dont_filter=True)
            #请求也可以用respinse.follow()方法，可以自动拼接链接，代码更简洁
            # yield response.follow(url=next_url, callback=self.comment_parse, meta={"movie_id": movie_id})
            # yield scrapy.Request(url=self.movie_comment_url_base % (movie_id) + next_url, callback=self.comment_parse, meta={"movie_id": movie_id})

        next_url = response.xpath('//div[@class="page"]/a[@title="下一页"]/@href').extract_first()
        if next_url:#如果有下一页的url就进行请求，并将请求结果返回给这个函数本身
            print("到要回调了，抓下一页 Request: {}".format(self.base_url + next_url))
            yield scrapy.Request(url=self.base_url + next_url, callback=self.parse,dont_filter=True)

        sleep(1)

    #进行详情页面的爬取，这里的respons接的是parse()中yield scrapy.Request()返回的内容
    @check_response
    def detail_parse(self, response):
        print('——1.1详情: {}'.format(response))
        print('*' * 30)
        #接收parse()中yield scrapy.Request()通过meta参数传递的"movie_id"
        movie_id = response.meta.get("movie_id")
        vid = response.xpath("//a/@data-vid").extract_first()
        transmit = response.xpath('//span[@class="share-counts v-center counts fs_12 fw_300 show"]/text()').extract_first() #转发数

        item = DetailItem()
        item["transmit"] = transmit
        item["movie_id"] = movie_id
        yield item

        #电影评论回调函数，评论获取的是一个json
        yield scrapy.Request(url=self.comment_base_url.format(movie_id), callback=self.comment_parse, meta={"movie_id": movie_id},dont_filter=True)
        # 电影播放链接回调函数，链接获取的是一个json，虽然渲染后的elements中有视频链接，但是source里没有，只能从XHR的json中获取
        # 这个json的链接中有几段像ID的搜一下就能找到对应的id了
        yield scrapy.Request(url=self.play_base_url.format(vid), callback=self.playurl_parse, meta={"movie_id": movie_id},dont_filter=True)

        sleep(1)

    #获取影片封面图
    @check_response
    def movie_image_parse(self,response):
        print('——1.2图片: {}'.format(response))
        print('*' * 30)
        item = MovieImage()
        item["movie_id"] = response.meta.get("movie_id")
        item["image"] = response
        yield item

    # 电影评论回调函数
    @check_response
    def comment_parse(self, response):
        print('——1.1.1评论: {}'.format(response))
        print('*' * 30)
        result = response.json()
        data = result.get("data")
        comments = data.get("list")

        for comment in comments:
            item = MovieCommentItem()
            item["content"] = comment.get("content")
            item['user_headurl'] = comment.get("userInfo").get("avatar")
            item['user_id'] = comment.get("userid")
            item['followee'] = comment.get("userInfo").get("count").get("count_followee")
            item['follower'] = comment.get("userInfo").get("count").get("count_follower")
            item['collected'] = comment.get("userInfo").get("count").get("count_collected")
            item['user_like'] = comment.get("userInfo").get("count").get("count_liked")
            item["movie_id"] = response.meta.get("movie_id")
            yield item
            yield scrapy.Request(comment.get("userInfo").get("avatar"),callback=self.userhead_parse,meta={"user_id": comment.get("userid")},dont_filter=True)

        # 如果评论存在下一页，不断获取下一页，直到获取结束,这里在爬取的时候很重要
        next_comment_url = data.get("next_page_url")
        if next_comment_url:
            yield scrapy.Request(url=self.comment_host_url + next_comment_url, callback=self.comment_parse,dont_filter=True)
        sleep(1)
    # 电影播放链接回调函数
    @check_response
    def playurl_parse(self, response):
        print('——1.1.2播放链接: {}'.format(response))
        print('*' * 30)
        result = response.json()
        plays = result.get("data").get("resource").get("progressive")
        for play in plays:
            item = MoviePlayUrlItem()
            item["profile"] = play.get("profile")
            item["url"] = play.get("url")
            # 从request 上获取 meta信息
            item["movie_id"] = response.meta.get("movie_id")
            yield item

    #获取用户头像
    @check_response
    def userhead_parse(self,response):
        print('——1.1.1.1用户头像: {}'.format(response))
        print('*' * 30)
        item = UserHead()
        item["user_id"] = response.meta.get("user_id")
        item["user_head"] = response
        yield item
